﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Xml;
using Notoric.Model.Internal;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// A common base class for brushes.
    /// </summary>
    public abstract class Brush : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Brush"/> instance.
        /// </summary>
        protected Brush()
        {
        }

        /// <summary>
        /// Creates a new <see cref="Brush"/> instance.
        /// </summary>
        /// <remarks>
        /// This constructor extracts the type information and the parent XML element.
        /// </remarks>
        /// <param name="element">The XML element that contains the data of this brush</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        protected Brush(XmlElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            this.element = element;
            this.typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];
            this.owner = (XmlElement)element.ParentNode;
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Ocurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, the method creates an XML element
        /// that backups the data of the brush.<br/>
        /// The common attributes (e.g. type) is added by the <see cref="Brush"/> class.
        /// </summary>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document used for creating the element and attributes.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateBrushElement(XmlDocument document);

        /// <summary>
        /// When overriden in a derived class, the method discards the attributes
        /// of the element that backed up the data of this brush.
        /// </summary>
        protected abstract void DiscardBrushElement();

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="Brush"/> instance.
        /// </summary>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the property whose value has changed.
        /// </para>
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Sets the XML element that directly owns the XML element of this brush.
        /// If the brush had a different owner before, it automatically removes itself
        /// from the owner.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that directly owns the XML element of this brush.
        /// </para>
        /// </param>
        internal void SetOwner(XmlElement owner)
        {
            if (this.owner != owner)
            {
                if (this.owner != null)
                    this.owner.RemoveChild(element);

                element = null;
                typeAttribute = null;
                DiscardBrushElement();

                this.owner = owner;
                if (owner != null)
                {
                    element = CreateBrushElement(owner.OwnerDocument);
                    typeAttribute = owner.OwnerDocument.CreateAttribute(PackageVocabulary.TypeAttribute);

                    typeAttribute.Value = GetType().FullName;
                    element.Attributes.Append(typeAttribute);
                    owner.AppendChild(element);
                }
            }
        }

        #endregion

        #region Internal Properties

        /// <summary>
        /// Gets the XML element that backups the data of this brush.
        /// </summary>
        internal XmlElement Element
        {
            get { return element; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a <see cref="Brush"/> from its serialized XML version.
        /// </summary>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the brush data.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Brush"/>
        /// </para>
        /// <para>
        /// The created brush.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        public static Brush Deserialize(XmlElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            if (element.LocalName == PackageVocabulary.NullElementName)
                return null;

            Assembly assembly = Assembly.GetExecutingAssembly();
            Type brushType = assembly.GetType(element.Attributes[PackageVocabulary.TypeAttribute].Value);

            Brush brush = (Brush)Activator.CreateInstance(brushType, new object[] { element });

            return brush;
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the XML element that backups the data of this brush.
        /// </summary>
        private XmlElement element;
        /// <summary>
        /// Holds the XML element that directly owns the element of this brush.
        /// </summary>
        private XmlElement owner;
        /// <summary>
        /// Holds the XML attribute that backups the type name of this brush.
        /// </summary>
        private XmlAttribute typeAttribute;

        #endregion
    }

    /// <summary>
    /// Holds data for a solid color brush.
    /// </summary>
    public class SolidColorBrush : Brush
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="SolidColorBrush"/> instance.
        /// </summary>
        public SolidColorBrush()
        {
        }

        /// <summary>
        /// Creates a new <see cref="SolidColorBrush"/> instance.
        /// </summary>
        /// <param name="color">
        /// <para>
        /// Type: <see cref="Notoric.Model.Elements.Color"/>
        /// </para>
        /// <para>
        /// The color of the brush.
        /// </para>
        /// </param>
        public SolidColorBrush(Color color)
        {
            this.color = color;
        }

        /// <summary>
        /// Creates a new <see cref="SolidColorBrush"/> instance.
        /// </summary>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this brush.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        public SolidColorBrush(XmlElement element)
            : base(element)
        {
            colorAttribute = element.Attributes[PackageVocabulary.ColorAttribute];
            color = Color.Parse(colorAttribute.Value);
        }

        #endregion

        #region Brush Members

        /// <inheritdoc/>
        protected override XmlElement CreateBrushElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.SolidColorBrushElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            colorAttribute = document.CreateAttribute(PackageVocabulary.ColorAttribute);
            colorAttribute.Value = color.ToString();

            element.Attributes.Append(colorAttribute);

            return element;
        }

        /// <inheritdoc/>
        protected override void DiscardBrushElement()
        {
            colorAttribute = null;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the color of this brush.
        /// </summary>
        public Color Color
        {
            get { return color; }
            set
            {
                color = value;
                if (colorAttribute != null)
                    colorAttribute.Value = color.ToString();

                RaisePropertyChanged("Color");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Color"/> property.
        /// </summary>
        private Color color;
        /// <summary>
        /// Holds the XML attribute that backups the <see cref="Color"/> property.
        /// </summary>
        private XmlAttribute colorAttribute;

        #endregion
    }
}
